package cn.wwcyc.service.impl;

import cn.wwcyc.dao.ModbusDao;
import cn.wwcyc.domain.TjHsy;
import cn.wwcyc.domain.TjSsHsl;
import cn.wwcyc.exception.WWException;
import cn.wwcyc.exception.WWExceptionEnum;
import cn.wwcyc.mapper.TjHsyMapper;
import cn.wwcyc.mapper.TjSsHslMapper;
import cn.wwcyc.service.TjSsHslService;
import cn.wwcyc.utils.MQTTUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.*;

/**
* @author aa
* @description 针对表【TJ_SS_HSL】的数据库操作Service实现
* @createDate 2025-03-12 15:15:39
*/
//这个类比较复杂，主要时因为要考虑并发时候的线程安全问题，所以要加锁同步。
//但是其实有个不用线程同步的简单的方法，就是停止任务的时候，scheduler和executor不要shutdown，而只要把hsyList清空即可。
    //有三类线程存在竞争关系：1.用户请求产生的线程，2.scheduler用于定时调度的线程，3.executor用于执行含水仪任务的线程池中的线程
    //其中主要是1和2存在竞争，3几乎不存在竞争
    //竞争主要有例如：
    //  用户停止或启动调度任务之间对scheduler变量存在竞争
    //  用户停止或启动调度任务与scheduler定时调度的线程对scheduler，executor，hsyList变量存在竞争

@Service
public class TjSsHslServiceImpl2 extends ServiceImpl<TjSsHslMapper, TjSsHsl>
    implements TjSsHslService{

    @Autowired
    TjHsyMapper tjHsyMapper;
    @Autowired
    TjSsHslMapper tjSsHslMapper;
    // 创建一个可变大小的线程池
//    Runnable timerTask = null; //定时任务
    private ScheduledExecutorService scheduler;//定时任务调度器，用于执行含水仪任务
    Runnable timerTask;
    private ScheduledFuture<?> scheduledFuture;
    private ThreadPoolExecutor executor;//线程池，用于执行含水仪任务
    private  final int CORE_POOL_SIZE = 10; // 核心线程数
    private  final int MAXIMUM_POOL_SIZE = 200; // 最大线程数
    private  final long KEEP_ALIVE_TIME = 120L; // 线程空闲后的存活时间: 120秒
    private final int INIT_PERIOD = 60; //含水率采集频率，60秒一次
    //    private List<TjHsy> hsyList = new ArrayList<>();
    private List<TjHsy> hsyList = new CopyOnWriteArrayList<>();//线程安全的list

    private int period = INIT_PERIOD;
    @Override
    synchronized public void addHsy(TjHsy  tjHsy){
        hsyList.add(tjHsy);
    }
    @Override
    synchronized public void delHsy(TjHsy  tjHsy){
        hsyList.remove(tjHsy);
    }
    @Override
    synchronized public void editHsy(TjHsy  tjHsy){
        for(int i = 0; i < hsyList.size(); i++){
            if(hsyList.get(i).getId().equals(tjHsy.getId())){
                hsyList.set(i, tjHsy);
                break;
            }
        }
//        System.out.println("----------------"+hsyList.toString());
    }
    @Override
    synchronized public void delHsyById(Long  id){
        for (int i = 0; i < hsyList.size(); i++) {
            if(hsyList.get(i).getId().equals(id)){
                TjHsy hsy = hsyList.remove(i);
//                System.out.println(hsy + "is removed!!");
                break;
            }
        }
//        System.out.println("----------------"+hsyList.toString());
    }
    @Override
    synchronized public void setPeriod(int period){
        this.period = period;
        if(scheduledFuture != null) {
            stopCjJob();
            startCjJob();
        }
    }
//含水仪定时任务，一分钟取一条实时数据。
//    @PostConstruct
    private void init(){
//        System.out.println("-------------HslServImpl2线程 id： " + Thread.currentThread().getId());
//        final TjSsHslServiceImpl ThisService = this;
        //获取含水仪列表
        List<TjHsy> hsyListFromDb = tjHsyMapper.selectList(null);
        hsyList.addAll(hsyListFromDb);
//        System.out.println("开始启动含水仪任务");
        // 创建一个无界队列
        BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();

        // 创建线程池，允许核心线程在空闲时超时终止
        executor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAXIMUM_POOL_SIZE,
                KEEP_ALIVE_TIME, TimeUnit.SECONDS,
                workQueue,
                new ThreadPoolExecutor.CallerRunsPolicy()); // 当队列满且线程数达到最大值时使用的策略
        // 使用ScheduledExecutorService定期提交任务到线程池scheduler，scheduler这个线程池只有一个线程，用于执行定时任务，
        // 注意，scheduler这个线程池并不用于执行含水仪任务，这个线程池其实只是一个定时器。
        //在定时任务submitTask中，将含水仪任务提交到线程池executor，executor这个线程池中的线程数量多，用于真正的并发执行各个含水仪任务。
        scheduler = Executors.newScheduledThreadPool(1);
//        ExecutorService executorService = Executors.newFixedThreadPool(1);
//        executorService
        timerTask = () -> {
            if(scheduledFuture.getDelay(TimeUnit.SECONDS) < 0){//如果超时，则不执行，直接返回
                System.out.println(new Date() + "----------含水率采集任务超时"+ scheduledFuture.getDelay(TimeUnit.SECONDS));
                return;
            }
//            try {
//                Thread.sleep(6000);
//            } catch (InterruptedException e) {
//                throw new RuntimeException(e);
//            }
            List<Future<TjSsHsl>> hslList = new ArrayList<>();
            //1.这里不同步其实也可以，影响不大。(重新考虑后，觉得这里说的不对！！！必须同步)
            // 也有小概率，比如在这里遍历的中间，hsyList.size()会变，
            // 尤其是其他线程在这个时候删除了某个元素，这是就可能产生indexoutofBounds的错误。
            // 所以要加synchronized
            //这里的同步锁不能用this，必须要用TjSsHslServiceImpl.this对象

            synchronized (TjSsHslServiceImpl2.this){
                for (int i = 0; i < hsyList.size(); i++) {
//                    final int finalI = i;
                    TjHsy tjHsySource = hsyList.get(i);
                    TjHsy tjHsy = new TjHsy();
                    //拷贝改这个对象,拷贝的原因也是因为线程同步的原因，否则可能会出现线程安全问题。
                    //只把拷贝的对象传给子线程，子线程不再需要原列表中的对象。
                    //因为在其他地方可能修改列表中的TjHsy对象，而在任务线程中要使用这个对象，为保证这个对象不出现竞争，所以为任务线程单独拷贝一份，
                    // 任务线程只使用这个拷贝的对象，原来的对象不论如何变化，都不会影响任务线程。
                    BeanUtils.copyProperties(tjHsySource, tjHsy);
                    Future<TjSsHsl> future = executor.submit(() -> {//含水仪任务放在这里
//                        TjHsy tjHsy = hsyList.get(finalI); //这个必须放外面，不能在里面再获取，否则会报indexoutofBounds的错误
//                    System.out.println(Thread.currentThread().getName() + "正在执行含水仪任务:" + tjHsy.getJh());
                        if (tjHsy != null) {
                            if(tjHsy.getRegtype() == null || tjHsy.getRegdatatp() == null){
                                throw new WWException(WWExceptionEnum.HSY_CONFIG_ERROR);
                            }
                            if (tjHsy.getRegtype() != 4) {
                                throw new WWException(WWExceptionEnum.HSY_CONFIG_ERROR);
                            }
                            if(tjHsy.getRegdatatp() != 2 &&  tjHsy.getRegdatatp() != 3
                                    && tjHsy.getRegdatatp() != 4 && tjHsy.getRegdatatp() != 5)
                                throw new WWException(WWExceptionEnum.HSY_CONFIG_ERROR);
                        }
                        try {
                            Number val = ModbusDao.readHoldingRegister(tjHsy.getIp(), 1, Integer.parseInt(tjHsy.getLocation()), tjHsy.getRegdatatp().intValue());
                            TjSsHsl tjSsHsl = new TjSsHsl();
                            tjSsHsl.setId(UUID.randomUUID().toString());
                            tjSsHsl.setJh(tjHsy.getJh());
                            tjSsHsl.setCapacity(val.longValue());
//                            tjSsHsl.setSshsl(val.doubleValue());
                            tjSsHsl.setCjsj(new java.util.Date());
                            return tjSsHsl;
                        } catch (Exception e) {
                            throw e;
                        }
                    });
                    hslList.add(future);
                }
            }

            //采集完成后，获取所有采集任务线程的结果，保存至hsls中
            List<TjSsHsl> hsls = new ArrayList<>();
            for (Future<TjSsHsl> future : hslList) {
                try {
                    System.out.println("future.get()"+future);
                    TjSsHsl tjSsHsl = future.get(period, TimeUnit.SECONDS);
                    this.calculateSsHsl(tjSsHsl);
//                    this.calculateJz(tjSsHsl);
                    hsls.add(tjSsHsl);
                } catch (TimeoutException e){
//                    synchronized (TjSsHslServiceImpl2.this){
//                        scheduler.shutdownNow(); // 强制终止任务
//                        scheduler = Executors.newScheduledThreadPool(1);
//                        scheduledFuture = scheduler.scheduleAtFixedRate(timerTask, 0, period, TimeUnit.SECONDS);
//                    }
                    e.printStackTrace();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
            // 批量插入数据库中
            saveBatch(hsls);
            //转为json
            StringBuilder msg = new StringBuilder();
            msg.append("[");
            for (TjSsHsl hsl : hsls) {
                msg.append(
                        "{\"jh\":\"").append(hsl.getJh())
                        .append("\",\"capacity\":").append(hsl.getCapacity())
                        .append(",\"sshsl\":").append(hsl.getSshsl())
                        .append(",\"cjsj\":").append(hsl.getCjsj().getTime())
                        .append("},");
            }
            //去掉最后一个逗号
            if(msg.length() > 0)
                msg.deleteCharAt(msg.length() - 1);
            msg.append("]");
            //发送mqtt
            MQTTUtil.sendMsg(msg);
        };
        // 每隔60秒调度一次timerTask
        scheduledFuture = scheduler.scheduleAtFixedRate(timerTask, 0, period, TimeUnit.SECONDS);
    }

    private void calculateSsHsl(TjSsHsl tjSsHsl) {
        double n1 = tjSsHsl.getCapacity();
        // 根据不同的电容值范围计算含水率
        double n2 = n1/10;
        double n = n2 - 120;
        double data_j = 0;
        if (n <= 1.7) {
            data_j = 0;
        } else if (n > 1.7 && n < 6.7) {
            data_j = 326.24 * n - 23.64 * n * n - 486.29;
        } else if (n >= 6.7 && n < 12.7) {
            data_j = 86.31 * n + 9.66 * n * n - 373.65;
        } else if (n >= 12.7 && n < 26.7) {
            data_j = 103.47 * n + 2.02 * n * n + 673.48;
        } else if (n >= 26.7 && n < 65.5) {
            data_j = 1.38 * n * n - 19.06 * n + 4475.9;
        } else if (n >= 65.5 && n < 91.65) {
            data_j = 0.06 * n * n - 2.25 * n + 9081.2;
        } else if (n >= 91.65 && n < 98.7) {
            data_j = 119.33 * n - 0.49 * n * n + 2560.6;
        } else if (n >= 98.7 && n < 131.7) {
            data_j = 29.04 * n - 0.1 * n * n + 7648.3;
        } else if (n >= 131.7 && n < 203) {
            data_j = 10.29 * n - 0.02 * n * n + 8735.1;
        } else {
            data_j = 9999;
        }
        tjSsHsl.setSshsl(data_j / 100);
    }

    //根据实时值计算时均和日均值，暂时不考虑
    private void calculateJz(TjSsHsl tjSsHsl) {
        //时间
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime oneDayAgo = now.minus(1, ChronoUnit.DAYS);
        LocalDateTime oneHourAgo = now.minus(1, ChronoUnit.HOURS);;
        Timestamp startDayTimestamp = Timestamp.valueOf(oneDayAgo);
        Timestamp startHourTimestamp = Timestamp.valueOf(oneDayAgo);
        Timestamp endTimestamp = Timestamp.valueOf(now);

        //小时数据均值
        QueryWrapper<TjSsHsl> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("jh", tjSsHsl.getJh());
        queryWrapper.ge("cjsj", startHourTimestamp);
        List<TjSsHsl> tjSsHslList = this.getBaseMapper().selectList(queryWrapper);
        tjSsHslList.add(tjSsHsl);
        double sjhsl = tjSsHslList.stream().mapToDouble(TjSsHsl::getSshsl).average().orElse(0);
        tjSsHsl.setSjhsl(sjhsl);

        //日度数据均值
        QueryWrapper<TjSsHsl> queryWrapperDay = new QueryWrapper<>();
        queryWrapperDay.eq("jh", tjSsHsl.getJh());
        queryWrapperDay.ge("cjsj", startDayTimestamp);
        List<TjSsHsl> tjSsHslListDay = this.getBaseMapper().selectList(queryWrapperDay);
        tjSsHslListDay.add(tjSsHsl);
        double rjhsl = tjSsHslListDay.stream().mapToDouble(TjSsHsl::getSshsl).average().orElse(0);
        tjSsHsl.setRjhsl(rjhsl);
    }

    synchronized public void startCjJob(){
        if(scheduledFuture != null){
            return;
        }
//        scheduler = Executors.newScheduledThreadPool(1);

        scheduledFuture = scheduler.scheduleAtFixedRate(timerTask, 0, period, TimeUnit.SECONDS);
    }
    synchronized public void stopCjJob() {
        if(scheduledFuture == null){
            return;
        }
        scheduledFuture.cancel(true);//这里还需要检查，可能cancel只是取消一个任务，并不是停止线程池！！！
        scheduledFuture = null;
//        hsyList.clear();
    }


    public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask timerTask = new TimerTask() {
            int i = 0;
            @Override
            public void run() {
                System.out.println("执行任务" + i++);
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        timer.schedule(timerTask, 0, 1000);
//        try {
//            Thread.sleep(20000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
    }
}




